/*****************************************************************************
PCI demo for DJGPP or 32-bit Watcom C, using 32-bit PCI BIOS

Chris Giese     <geezer@execpc.com>     http://my.execpc.com/~geezer
Release date: Feb 23, 2005
This code is public domain (no copyright).
You can do whatever you want with it.
*****************************************************************************/
#include <string.h>
#include <stdio.h>
#if 0
/* C99 fixed-width types */
#include <stdint.h>
#else
typedef unsigned char	uint8_t;
typedef unsigned short	uint16_t;
typedef unsigned long	uint32_t;
#endif

#if defined(__DJGPP__)
#include <sys/nearptr.h> /* __djgpp_conventional_base, __djgpp_nearptr_enable() */
#include <crt0.h> /* _CRT0_FLAG_NEARPTR, _crt0_startup_flags */
#include <dpmi.h> /* __dpmi_...() */

#define	cdecl	/* nothing */

#elif defined(__WATCOMC__)
#if !defined(__386__)
#error This is a 32-bit program. Compile with WCC386
#endif
#include <dos.h>

/* zero segment base if using CauseWay DOS extender */
#define	__djgpp_conventional_base	0

#else
#error Unsupported compiler
#endif

typedef struct
{
	unsigned char bus, dev, fn;
} pci_t;

/* IMPORTS
from PCI32A.ASM */
int cdecl pci_detect_help(unsigned long entry, unsigned long virt_to_phys);
int cdecl pci_read_config_byte(pci_t *pci, unsigned reg, unsigned char *val);
int cdecl pci_read_config_word(pci_t *pci, unsigned reg, unsigned short *val);
int cdecl pci_read_config_dword(pci_t *pci, unsigned reg, unsigned long *val);
//int pci_write_config_byte(pci_t *pci, unsigned reg, unsigned char val);
//int pci_write_config_word(pci_t *pci, unsigned reg, unsigned short val);
//int pci_write_config_dword(pci_t *pci, unsigned reg, unsigned long val);

unsigned g_last_pci_bus;
/*****************************************************************************
*****************************************************************************/
static int pci_detect(void)
{
	unsigned len, i;//, ver;
	unsigned long offset, entry;
	unsigned char *adr, csum;

#if defined(__DJGPP__)
/* turn off data segment limit, for nearptr access */
	if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR))
	{
		if(!__djgpp_nearptr_enable())
		{
			printf("Can't enable near pointer "
				"access (WinNT/2k/XP?)\n");
			return -1;
		}
	}
#endif
	printf("BIOS32...");
/* the spec says it's in this range, aligned on a 16-byte boundary.
Code takes less than 8K and is position-independent (see below). */
	for(offset = 0xE0000; offset < 0xFFFFF; offset += 16)
	{
		adr = (unsigned char *)(offset + __djgpp_conventional_base);
/* bytes 0-3: signature */
		if(*(unsigned long *)(adr + 0) != 0x5F32335F) /* "_32_" */
			continue;
/* byte 9: structure len (in 16-byte paragraphs) */
		len = adr[9] * 16;
		if(len == 0)
			continue;
/* byte 10: checksum (checksum of entire structure must be 0) */
		csum = 0;
		for(i = 0; i < len; i++)
			csum += adr[i];
		if(csum)
			continue;
/* byte 8: BIOS32 revision */
		i = adr[8];
		if(i)
		{
			printf("unsupported revision %u "
				"(should be 0)\n...", i);
			continue;
		}
/* found it! */
		goto FOUND;
	}
	printf("not found\n");
	return -1;
FOUND:
/* bytes 4-7: BIOS32 entry point */
	entry = *(unsigned long *)(adr + 4);
	printf("entry point at 0x%lX\n", entry);
/* adding the virtual entry point with __djgpp_conventional_base
works because the BIOS32 code is position-independent.

Both BIOS32 and the PCIBIOS need these constraints:
- DS and CS must have the same base address
- DS and CS base and limit must "encompass" the memory range
  used by the BIOS32/PCIBIOS code
- Calling environment must allow I/O (IOPL <= CPL)
- At least 1K of stack */
	if(pci_detect_help(entry, __djgpp_conventional_base))
	{
		printf("not found\n");
		return -1;
	}
	printf("last bus=%u\n", g_last_pci_bus);
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static int pci_iterate(pci_t *pci)
{
	unsigned char hdr_type = 0x80;

/* if first function of this device, check if multi-function device
(otherwise fn==0 is the _only_ function of this device) */
	if(pci->fn == 0)
	{
		if(pci_read_config_byte(pci, 0x0E, &hdr_type))
			return -1;	/* error */
	}
/* increment iterators
fn (function) is the least significant, bus is the most significant */
	pci->fn++;
	if(pci->fn >= 8 || (hdr_type & 0x80) == 0)
	{
		pci->fn = 0;
		pci->dev++;
		if(pci->dev >= 32)
		{
			pci->dev = 0;
			pci->bus++;
			if(pci->bus > g_last_pci_bus)
				return 1; /* done */
		}
	}
	return 0;
}
/*****************************************************************************
*****************************************************************************/
int main(void)
{
	pci_t pci;
	int err;

/* check for PCI BIOS */
	if(pci_detect())
		return 1;
/* display numeric ID of all PCI devices detected */
	memset(&pci, 0, sizeof(pci));
	do
	{
		unsigned long id;

/* 00=PCI_VENDOR_ID */
		err = pci_read_config_dword(&pci, 0x00, &id);
		if(err)
ERR:		{
			printf("Error 0x%02X reading PCI config\n", err);
			return 1;
		}
/* anything there? */
		if(id != 0xFFFFFFFFL)
		{
			printf("bus %u, device %2u, function %u: "
				"device=%04lX:%04lX\n", pci.bus,
				pci.dev, pci.fn, id & 0xFFFF, id >> 16);
		}
	} while(!pci_iterate(&pci));
/* find a USB controller */
	memset(&pci, 0, sizeof(pci));
	do
	{
		unsigned char major, minor;

/* 0B=class */
		err = pci_read_config_byte(&pci, 0x0B, &major);
		if(err)
			goto ERR;
/* 0A=sub-class */
		err = pci_read_config_byte(&pci, 0x0A, &minor);
		if(err)
			goto ERR;
/* anything there? */
		if(major != 0xFF || minor != 0xFF)
		{
printf("detected device of class %u.%u\n", major, minor);
			if(major == 12 && minor == 3)
			{
				printf("USB controller detected\n");
				break;
			}
		}
	} while(!pci_iterate(&pci));
	return 0;
}
